package Navi_run;

import Navi_utils.GeoHash;
import encryption.Hash;
import it.unisa.dia.gas.jpbc.Element;
import objects.LocationBean;
import org.ujmp.core.Matrix;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.*;

import static encryption.Fuzzy_search.*;
import static encryption.Fuzzy_search.Enc_Query;
import static encryption.Opaak.generate_ZKPK;
import static parameter.parameter_length.r1r2_length;
import static utils.Util.xor;

public class driver_generate {
    /**
     *
     * @param locationBeans 查询点的经纬度集合
     * @param si 司机秘密
     * @param te 时间历元
     * @param rci 请求次数
     * @param rou 零知识证明用ρ
     * @param F 零知识证明用F
     * @param Ei1 TA给司机的签名
     * @param Ei2 TA给司机的签名
     * @return 导航查询NQ
     */
    public static Map<String, Object> generate_Query(List<LocationBean> locationBeans,Matrix SK1,Matrix SK2,Matrix V, BigInteger si, int te, int rci, BigInteger rou, BigInteger F, Element Ei1, Element Ei2) {
        try {
            SecureRandom secureRandom = new SecureRandom();
            //生成请求假名
            String temp = "" + te + rci;
            BigInteger grlpi_temp = params.pairing.getZr().newElement().setFromHash(temp.getBytes(), 0, temp.getBytes().length).toBigInteger();
            BigInteger grlpi = grlpi_temp.modPow(((F.subtract(BigInteger.ONE)).divide(rou)), F);
            BigInteger rlpi = grlpi.modPow(si, F);
            //生成零知识证明
            Map<String, BigInteger> ZKPK = generate_ZKPK(si, F, grlpi, secureRandom);
            //生成Mi
            double[][] Index_EncKI = generate_IndexEncTable(locationBeans,SK1,SK2,V,true);
            //这里还要加个Pi 暂时没加
            Map<String,Object> Mi = new HashMap<String, Object>();
            Mi.put("Index_Enc",Index_EncKI);
            Mi.put("rlpi",rlpi);
            //生成其他数据
            //随机选择r1将(Ei1,Ei2)随机化
            BigInteger r1 = new BigInteger(r1r2_length, secureRandom);
            Element REi1 = Ei1.pow(r1).getImmutable();
            Element REi2 = Ei2.pow(r1).getImmutable();
            //随机选择r2 计算e(E0i1, B)^r2 ← e(Ei1, B)^r1r2
            BigInteger r2 = new BigInteger(r1r2_length, secureRandom);
            // 计算Ei = e(REi1, B)^r2
            Element Ei = params.pairing.pairing(REi1, params.gpk_B).pow(r2).getImmutable();
            //计算ci = H(REi1, REi2, e(REi1, B)^r2, Mi)
            String Mi_str = Arrays.deepToString((double[][])Mi.get("Index_Enc")) + ((BigInteger)Mi.get("rlpi")).toString();
            temp = REi1.toString() + REi2.toString() + Ei.toString() + Mi_str + rci;
            BigInteger ci = new BigInteger(Hash.sha256(temp));
            //计算ssi = r2 + cisi
            BigInteger ssi = r2.add(ci.multiply(TA_driver.si));
            //建立NQ 并发送给rsu
            Map<String, Object> NQ = new HashMap<>();
            NQ.put("ZKPK",ZKPK);
            NQ.put("Mi", Mi);
            NQ.put("REi1", REi1);
            NQ.put("REi2", REi2);
            NQ.put("ci", ci);
            NQ.put("ssi", ssi);
            TA_driver.rci++;
            return NQ;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @param locationBean 司机当前位置索引
     * @param Mj 司机之间握手生成的集合
     * @param si 司机秘密
     * @param te 时间历元
     * @param rci 请求次数
     * @param rou 零知识证明用ρ
     * @param F 零知识证明用F
     * @param Ei1 TA给司机的签名
     * @param Ei2 TA给司机的签名
     * @return 交通报告DR
     */
    public static Map<String, Object> generate_report(LocationBean locationBean,Map<String, Object> Mj,Matrix SK1,Matrix SK2,Matrix V, BigInteger si, int te, int rci, BigInteger rou, BigInteger F, Element Ei1, Element Ei2) {
        try {
            SecureRandom secureRandom = new SecureRandom();
            List<LocationBean> locationBeans = new ArrayList<>();
            locationBeans.add(locationBean);
            //生成请求假名
            String temp = "" + te + rci;
            BigInteger grlpi_temp = params.pairing.getZr().newElement().setFromHash(temp.getBytes(), 0, temp.getBytes().length).toBigInteger();
            BigInteger grlpi = grlpi_temp.modPow(((F.subtract(BigInteger.ONE)).divide(rou)), F);
            BigInteger rlpi = grlpi.modPow(si, F);
            //生成零知识证明
            Map<String, BigInteger> ZKPK = generate_ZKPK(si, F, grlpi, secureRandom);

            double[][] Query_EncKI = generate_IndexEncTable(locationBeans,SK1,SK2,V,false);;
            //这里还要加个Pi 暂时没加
            //生成其他数据
            //随机选择r1将(Ei1,Ei2)随机化
            BigInteger r1 = new BigInteger(r1r2_length, secureRandom);
            Element REi1 = Ei1.pow(r1).getImmutable();
            Element REi2 = Ei2.pow(r1).getImmutable();
            //随机选择r2 计算e(E0i1, B)^r2 ← e(Ei1, B)^r1r2
            BigInteger r2 = new BigInteger(r1r2_length, secureRandom);
            // 计算Ei = e(REi1, B)^r2
            Element Ei = params.pairing.pairing(REi1, params.gpk_B).pow(r2).getImmutable();
            //计算ci = H(REi1, REi2, e(REi1, B)^r2, Mi)
            //这里是要加Mj的 暂时没加
            temp = REi1.toString() + REi2.toString() + Ei.toString() + rci;
            BigInteger ci = new BigInteger(Hash.sha256(temp));
            //计算ssi = r2 + cisi
            BigInteger ssi = r2.add(ci.multiply(TA_driver.si));

            //建立NQ 并发送给rsu
            Map<String, Object> DR = new HashMap<>();
            DR.put("ZKPK",ZKPK);
            DR.put("Mj", Mj);
            DR.put("Query_EncKI",Query_EncKI);
            DR.put("REi1", REi1);
            DR.put("REi2", REi2);
            DR.put("ci", ci);
            DR.put("ssi", ssi);
//            System.out.println("Mj" + Mj);
//            System.out.println("REi1" + REi1);
//            System.out.println("REi2" + REi2);
//            System.out.println("ci" + ci);
//            System.out.println("ssi" + ssi);
//            System.out.println("rci" + TA_driver.rci);
            TA_driver.rci++;
            return DR;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param locationBeans 经纬度列表
     * @param flag          如果flag==true 就是请求司机的EncIndex 否则是贡献司机EncQuery，贡献司机的经纬度列表只有一项
     * @return Index_EncSK(KI) / Index_EncQu(KI)
     */
    public static double[][] generate_IndexEncTable(List<LocationBean> locationBeans,Matrix SK1,Matrix SK2,Matrix V, boolean flag) {
        byte[] key = null;
        double[][] EncKI = new double[locationBeans.size() * 2][(int) V.getColumnCount()];
        Map<String, Matrix> EncIndex;
        for (int i = 0; i < locationBeans.size() * 2; i = i + 2) {
            double[] I = BuildIndex(locationBeans.get(i / 2), key, lshMinHash);
            if (flag) {
                EncIndex = Enc_Index(SK1, SK2, V, I);
                //把加密结果EncIndex保存到EncKI中
                Matrix SK1_I1 = EncIndex.get("SK1_I1").transpose();
                Matrix SK2_I2 = EncIndex.get("SK2_I2").transpose();
                for (int j = 0; j < SK1_I1.getColumnCount(); j++) {
                    EncKI[i][j] = SK1_I1.getAsDouble(0, j);
                    EncKI[i + 1][j] = SK2_I2.getAsDouble(0, j);
                }
            } else {
                EncIndex = Enc_Query(SK1, SK2, V, I);
                //把加密结果EncIndex保存到EncKI中
                Matrix SK1_I1 = EncIndex.get("SK1_Q1").transpose();
                Matrix SK2_I2 = EncIndex.get("SK2_Q2").transpose();
                for (int j = 0; j < SK1_I1.getColumnCount(); j++) {
                    EncKI[i][j] = SK1_I1.getAsDouble(0, j);
                    EncKI[i + 1][j] = SK2_I2.getAsDouble(0, j);
                }
            }

        }
        return EncKI;
    }

    /**
     *
     * @param indj 位置索引
     * @param te 时间历元
     * @param daiertaru Rsu广播的黛儿塔ru
     * @return 返回自身pidj
     */
    public static byte[] generate_pidj(LocationBean indj,int te, int r3,String daiertaru) {
        try {
            GeoHash geoHash = new GeoHash(indj);
            geoHash.getGeoHashBase32();
            byte[] pidj = Hash.sha256_byte(""+r3+geoHash.getGeoHashBase32() + daiertaru + te);
            params.indj = indj;
            params.pidjs.add(pidj);
            params.pidj = pidj;
            return pidj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @param indj 位置索引
     * @param K 密钥
     * @param te 时间历元
     * @param daiertaru Rsu广播的黛儿塔ru
     * @return 返回自身hsvj
     */
    public static byte[] generate_hsvj(LocationBean indj, int K,int te, String daiertaru) {
        try {
            GeoHash geoHash = new GeoHash(indj);
            geoHash.getGeoHashBase32();
            byte[] hsvj = Hash.sha256_byte("" + K + geoHash.getGeoHashBase32()+daiertaru+te);
            params.hsvjs.add(hsvj);
            params.hsvj = hsvj;
            return hsvj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @param indj 自身位置索引
     * @param hsvj 自身hsvj
     * @param pidjs 收到的pidj合
     * @param hsvjs 收到的hsvj合
     * @return Mj
     */
    public static Map<String, Object> generate_Mj(LocationBean indj, byte[] hsvj, List<byte[]> pidjs, List<byte[]> hsvjs) {
        try {
            Map<String, Object> Mj = new HashMap<>();
            byte[] bytes = new byte[hsvjs.get(0).length];
            //连续异或所有接收到的hsvj
            for (byte[] temp_hsvj : hsvjs) {
                bytes = xor(bytes, temp_hsvj);
            }
            //建立Mj
            Mj.put("indj", indj);
            Mj.put("pidjs", pidjs);
            //自己生成的hsvj
            Mj.put("hsvj", hsvj);
            //接收到的hsvj的异或结果
            Mj.put("xor_hsvjs", bytes);
            return Mj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        List<LocationBean> locationBeans = new ArrayList<LocationBean>();
        LocationBean locationBean = new LocationBean(35.665553, 48.6666456);
        locationBeans.add(locationBean);
        locationBean.setLat(35.665543);
        locationBean.setLng(35.665543);
        locationBeans.add(locationBean);
        generate_Query(locationBeans,TA_driver.SK1,TA_driver.SK2,TA_driver.V, TA_driver.si, 155555, TA_driver.rci, params.ZKPK_rou, params.ZKPK_F, TA_driver.driver_Ei1, TA_driver.driver_Ei2);
    }

}
